/** @file parser_util.h
 *
 *  @author Dongryeol Lee (drselee@gmail.com)
 */

#ifndef CORE_DATA_PARSER_UTIL_H
#define CORE_DATA_PARSER_UTIL_H

namespace core {
namespace data {

class ParserUtil {
  private:

    static bool is_char_to_be_consumed_(char c) {
      return isspace(c) || c == ',';
    }

    /** @brief Returns true if the last white space consumed is a
    *          new line character or a EOF.
    */
    static bool ConsumeSpaces_(std::ifstream &stream) {
      bool new_line_encountered = false;
      do {
        char new_char;
        std::streampos save_pos = stream.tellg();
        if((! stream.good()) || stream.eof()) {
          new_line_encountered = true;
          break;
        }
        stream.read(&new_char, 1);
        if(is_char_to_be_consumed_(new_char)) {
          if(new_char == '\n') {
            new_line_encountered = true;
            break;
          }
        }
        else {

          // Rewind the stream so that it points to the first
          // non-white-space/comma character.
          stream.seekg(save_pos);
          break;
        }
      }
      while(true);
      return new_line_encountered;
    }

    static bool GetToken_(
      std::ifstream &stream, std::string *token_out,
      bool *is_numeric_out) {

      // Consume the whitespace/comma in the before the token.
      if(ConsumeSpaces_(stream)) {
        return true;
      }

      // Consume until a space or a comma is encountered.
      if(token_out) {
        token_out->clear();
      }
      *is_numeric_out = true;
      do {
        char new_char;
        std::streampos save_pos = stream.tellg();
        if((! stream.good()) || stream.eof()) {
          break;
        }
        stream.read(&new_char, 1);
        if(! is_char_to_be_consumed_(new_char)) {
          if(token_out) {
            token_out->append(&new_char, 1);
          }
          (*is_numeric_out) = (*is_numeric_out) && isdigit(new_char);
        }
        else {

          // Rewind the stream so that it points to the first
          // white-space character.
          stream.seekg(save_pos);
          break;
        }
      }
      while(true);

      // Now consume the rest of the whitespace. A new line denotes
      // the end of the line, i.e. the last token.
      return ConsumeSpaces_(stream);
    }

  public:

    static int CountNumLines(
      std::ifstream &stream, int check_num_tokens_in,
      int begin_char_in, int count_in,
      bool check_token_count_in,
      int *failed_line_num_out,
      int * num_attributes_out) {

      if(num_attributes_out) {
        *num_attributes_out = 0;
      }
      if(begin_char_in >= 0) {
        stream.seekg(begin_char_in);
      }
      std::streampos save_pos = stream.tellg();
      * failed_line_num_out = -1;
      int num_endlines = 0;
      int num_chars_consumed = 0;
      std::vector< std::string > token_list;
      while(stream.good() && num_chars_consumed < count_in) {
        int prev_pos = stream.tellg();
        int num_tokens =
          core::data::ParserUtil::ParseLine(stream, &token_list);
        num_chars_consumed += (static_cast<int>(stream.tellg()) - prev_pos);
        if(num_tokens > 0) {
          if(num_attributes_out) {

            // The attribute guess is the last index:value pair in the
            // token list. index + 1 is the answer.
            int attribute_guess = atoi(token_list.back().c_str()) + 1;
            (*num_attributes_out) =
              std::max(*num_attributes_out, attribute_guess);
          }
          if(check_token_count_in && num_tokens != check_num_tokens_in) {
            * failed_line_num_out = num_endlines;
            num_endlines = -1;
            break;
          }
          else {
            num_endlines++;
          }
        }
      }
      stream.clear();
      stream.seekg(save_pos);
      return num_endlines;
    }

    static int ParseLine(
      std::ifstream &stream, std::vector< std::string > *token_list_out) {

      bool last_token = false;
      if(token_list_out) {
        token_list_out->clear();
      }
      std::string token;
      int num_tokens = 0;
      do {
        bool is_numeric = false;
        last_token =
          GetToken_(stream, &token , &is_numeric);
        if(token.length() > 0) {
          if(token_list_out) {
            token_list_out->push_back(token);
          }
          num_tokens++;
        }
      }
      while(! last_token);
      return num_tokens;
    }
};
}
}

#endif
